using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;

public class BundleEditor
{
    private static string BundleTargetPath = Application.streamingAssetsPath;
    private static string AbConfigPath = "Assets/Editor/AbConfig.asset";

    /// <summary>
    /// key 是 ab包名，value 是 路径，所有文件夹 ab包 dic
    /// </summary>
    private static Dictionary<string, string> AllFileDic = new Dictionary<string, string>();

    /// <summary>
    /// 过滤的list，当前所有ab包路径集合
    /// </summary>
    private static List<string> AllFileAbPath = new List<string>();

    /// <summary>
    /// 所有单个 prefab 的 ab包 dic
    /// </summary>
    private static Dictionary<string, List<string>> AllPrefabDic = new Dictionary<string, List<string>>();

    /// <summary>
    /// 储存所有有效路径
    /// </summary>
    private static List<string> ConfigFil = new List<string>();

    [MenuItem("Tools/打包")]
    private static void Build()
    {
        AllFileDic.Clear();
        AllFileAbPath.Clear();
        AllPrefabDic.Clear();
        ConfigFil.Clear();

        AbConfig abConfig = AssetDatabase.LoadAssetAtPath<AbConfig>(AbConfigPath);

        foreach (var fileDic in abConfig.allFileDicAb)
        {
            if (AllFileDic.ContainsKey(fileDic.AbName))
            {
                Debug.LogError("AB包配置名字重复，请检查! ");
            }
            else
            {
                AllFileDic.Add(fileDic.AbName, fileDic.Path);
                AllFileAbPath.Add(fileDic.Path);
                ConfigFil.Add(fileDic.Path);
            }
        }

        // 获取 文件夹下 所有 prefab 的 GUID
        string[] allStr = AssetDatabase.FindAssets("t:Prefab", abConfig.allPrefabPath.ToArray());
        for (var i = 0; i < allStr.Length; i++)
        {
            // 通过 GUID 获取 资源路径
            string path = AssetDatabase.GUIDToAssetPath(allStr[i]);
            ConfigFil.Add(path);

            // 显示进度条
            EditorUtility.DisplayProgressBar("查找Prefab", "Prefab:" + path, i * 1.0f / allStr.Length);

            if (!ContainAllFileAb(path))
            {
                GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);

                // 获取 当前资源 的 所有依赖资源 路径
                string[] allDepend = AssetDatabase.GetDependencies(path);

                // 需要 加载的 所有依赖资源 路径
                List<string> allDependPath = new List<string>();

                for (var j = 0; j < allDepend.Length; j++)
                {
                    if (allDepend[j].EndsWith(".cs"))
                    {
                        // 过滤脚本
                        continue;
                    }

                    if (ContainAllFileAb(allDepend[j]))
                    {
                        // 过滤其他 ab 包中的资源
                        continue;
                    }

                    // 加入依赖资源
                    AllFileAbPath.Add(allDepend[j]);
                    allDependPath.Add(allDepend[j]);
                }

                if (AllPrefabDic.ContainsKey(obj.name))
                {
                    Debug.LogError("存在相同名字的Prefab！名字：" + obj.name);
                }
                else
                {
                    AllPrefabDic.Add(obj.name, allDependPath);
                }
            }
        }

        // 设置所有 文件夹 的 ab包名
        foreach (var name in AllFileDic.Keys)
        {
            SetAbName(name, AllFileDic[name]);
        }

        // 设置 所有 单个 prefab 的 ab包名
        foreach (var name in AllPrefabDic.Keys)
        {
            SetAbName(name, AllPrefabDic[name]);
        }

        // 构建AB包
        BuildAssetBundle();

        // 清除所有AB包名
        string[] oldAbNames = AssetDatabase.GetAllAssetBundleNames();
        for (var i = 0; i < oldAbNames.Length; i++)
        {
            AssetDatabase.RemoveAssetBundleName(oldAbNames[i], true);
            EditorUtility.DisplayProgressBar("清除AB包名", "名字:" + oldAbNames[i], i * 1.0f / oldAbNames.Length);
        }

        // 编辑器 资源刷新
        AssetDatabase.Refresh();

        // 清空进度条
        EditorUtility.ClearProgressBar();
    }

    /// <summary>
    /// 给 资源 设置 AB包名
    /// </summary>
    /// <param name="name"></param>
    /// <param name="path"></param>
    static void SetAbName(string name, string path)
    {
        AssetImporter assetImporter = AssetImporter.GetAtPath(path);
        if (assetImporter == null)
        {
            Debug.LogError("不存在此路径文件：" + path);
        }
        else
        {
            assetImporter.assetBundleName = name;
        }
    }

    /// <summary>
    /// 给 资源 设置 AB包名
    /// </summary>
    /// <param name="name"></param>
    /// <param name="paths"></param>
    static void SetAbName(string name, List<string> paths)
    {
        for (var i = 0; i < paths.Count; i++)
        {
            SetAbName(name, paths[i]);
        }
    }

    /// <summary>
    /// 构建AB包
    /// </summary>
    static void BuildAssetBundle()
    {
        string[] allBundles = AssetDatabase.GetAllAssetBundleNames();

        // key 为全路径，value 为包名
        Dictionary<string, string> resPathDic = new Dictionary<string, string>();
        for (var i = 0; i < allBundles.Length; i++)
        {
            // 获取该ab包下，所有资源路径
            string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(allBundles[i]);
            for (var j = 0; j < allBundlePath.Length; j++)
            {
                if (allBundlePath[j].EndsWith(".cs"))
                    continue;

                Debug.Log("此AB包：" + allBundles[i] + "下面包含的资源文件路径：" + allBundlePath[j]);

                // 检测有效路径
                if (ValidPath(allBundlePath[j]))
                {
                    resPathDic.Add(allBundlePath[j], allBundles[i]);
                }
            }
        }

        // 删除无用AB包
        DeleteAb();

        // 生成自己的配置表
        WriteData(resPathDic);

        // 构建 AssetBundle 包
        BuildPipeline.BuildAssetBundles(BundleTargetPath, BuildAssetBundleOptions.ChunkBasedCompression,
            EditorUserBuildSettings.activeBuildTarget);
    }

    /// <summary>
    /// 生成自己的配置表
    /// </summary>
    /// <param name="resPathDic">key 为全路径，value 为包名</param>
    static void WriteData(Dictionary<string, string> resPathDic)
    {
        AssetBundleConfig config = new AssetBundleConfig();
        config.AbList = new List<AbBase>();

        // 配置资源相关数据
        foreach (var path in resPathDic.Keys)
        {
            AbBase abBase = new AbBase();
            abBase.Path = path;
            abBase.Crc = Crc32.GetCrc32(path);
            abBase.AbName = resPathDic[path];
            abBase.AssetName = path.Remove(0, path.LastIndexOf("/", StringComparison.Ordinal) + 1);
            abBase.AbDependence = new List<string>();

            // 获取所有依赖资源路径
            string[] resDependence = AssetDatabase.GetDependencies(path);
            for (var i = 0; i < resDependence.Length; i++)
            {
                // 检测依赖资源路径
                string tempPath = resDependence[i];

                // 依赖资源是自身资源，依赖资源为脚本，则过滤
                if (tempPath == path || tempPath.EndsWith(".cs"))
                    continue;

                // 检测依赖ab包名
                if (!resPathDic.TryGetValue(tempPath, out var abName))
                {
                    // 如果没找到 有效 依赖资源的ab包，则
                    // 检测 依赖资源 ab包 不是 自身 ab包 的情况
                    AssetImporter assetImporter = AssetImporter.GetAtPath(tempPath);
                    if (assetImporter?.assetBundleName != abBase.AbName)
                    {
                        abName = assetImporter?.assetBundleName;
                    }
                }

                if (abName != default)
                {
                    // 依赖资源的ab包是自身ab包，则过滤
                    if (abName == resPathDic[path])
                        continue;

                    // 过滤重复依赖ab包
                    if (!abBase.AbDependence.Contains(abName))
                    {
                        abBase.AbDependence.Add(abName);
                    }
                }
            }

            config.AbList.Add(abBase);
        }

        // 写入xml
        string xmlPath = Application.dataPath + "/AssetBundleConfig.xml";

        FileStream fileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
        StreamWriter sw = new StreamWriter(fileStream, Encoding.UTF8);

        XmlSerializer xs = new XmlSerializer(config.GetType());
        xs.Serialize(sw, config);

        sw.Close();
        fileStream.Close();

        // 写入二进制
        // 清空path，减小ab包体积
        foreach (var abBase in config.AbList)
        {
            abBase.Path = "";
        }

        string bytePath = "Assets/GameData/Data/AbData/AssetBundleConfig.bytes";

        FileStream fs = new FileStream(bytePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(fs, config);
        fs.Close();
    }

    /// <summary>
    /// 删除无用AB包
    /// </summary>
    static void DeleteAb()
    {
        string[] allBundlesMame = AssetDatabase.GetAllAssetBundleNames();
        DirectoryInfo directory = new DirectoryInfo(BundleTargetPath);

        FileInfo[] files = directory.GetFiles("*", SearchOption.AllDirectories);
        for (var i = 0; i < files.Length; i++)
        {
            if (files[i].Name.EndsWith(".meta") ||
                ContainAbName(files[i].Name.Replace(".manifest", ""), allBundlesMame))
                continue;

            Debug.Log("此AB包已经被删或者改名：" + files[i].Name);

            if (File.Exists(files[i].FullName))
            {
                File.Delete(files[i].FullName);
            }
        }
    }

    /// <summary>
    /// 遍历文件夹里的文件名与设置的所有AB包进行检查判断
    /// </summary>
    /// <param name="name"></param>
    /// <param name="strs"></param>
    /// <returns></returns>
    static bool ContainAbName(string name, string[] strs)
    {
        for (var i = 0; i < strs.Length; i++)
        {
            if (name == strs[i])
                return true;
        }

        return false;
    }

    /// <summary>
    /// 是否 包含 在已经有的 AB包里，用来做 AB包 冗余剔除
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    static bool ContainAllFileAb(string path)
    {
        for (var i = 0; i < AllFileAbPath.Count; i++)
        {
            if (path == AllFileAbPath[i] ||
                (path.Contains(AllFileAbPath[i]) && path.Replace(AllFileAbPath[i], "")[0] == '/'))
                return true;
        }

        return false;
    }

    /// <summary>
    /// 是否有效路径
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    static bool ValidPath(string path)
    {
        for (var i = 0; i < ConfigFil.Count; i++)
        {
            if (path == ConfigFil[i] || path.Contains(ConfigFil[i]) && path.Replace(ConfigFil[i], "")[0] == '/')
            {
                return true;
            }
        }

        return false;
    }
}